package matrices;

public class DenseMatrix implements Matrix{
	
	private static IllegalArgumentException
		rowColException = new IllegalArgumentException("Row or Column number illegal");
	
	final int rows, columns;
	MatrixElement [][] values;
	MatrixElement defaultElement;
	
	private void checkLegalRow(int rows) throws IllegalArgumentException{
		if(rows < 0 || rows > (this.rows-1) ) throw rowColException;
	}
	
	private void checkLegalCol(int col) throws IllegalArgumentException{
		if(col < 0 || col > (this.columns -1)) throw rowColException;
	}
	
	private void checkLegalCoords(int rows, int columns) throws IllegalArgumentException{
		checkLegalRow(rows); checkLegalCol(columns);
	}
	
	public DenseMatrix(int rows, int columns, MatrixElement defaultElement) throws IllegalArgumentException{
		this.rows = rows;
		this.columns = columns;
		checkLegalCoords(rows - 1, columns - 1);
		this.values = new MatrixElement[rows][columns];
		this.defaultElement = defaultElement.createNewInstance();
		for(int i = 0 ; i < rows ; i++)
			for(int j = 0 ; j < columns ; j++)
				values[i][j] = defaultElement.createNewInstance();
	}
	
	public int getRows() { return this.rows; }
	
	public int getColumns() { return this.columns; }
	
	public MatrixElement getElementValueAt(int row, int column)
			throws IllegalArgumentException {
		checkLegalCoords(row,column);
		return this.values[row][column].createNewInstance();
	}

	public MatrixElement getElementPointerAt(int row, int column) 
			throws IllegalArgumentException{
		checkLegalCoords(row, column);
		return this.values[row][column];
	}
	
	public MatrixElement getDefaultElementValue() {
		return this.defaultElement.createNewInstance();
	}
	
	public MatrixElement getDefaultElementPointer() {
		return this.defaultElement;
	}
	
	public void setElementValueAt(int row, int column, MatrixElement newElement)
			throws IllegalArgumentException {
		checkLegalCoords(row,column);
		this.values[row][column] = newElement.createNewInstance();
	}
	
	public void setElementPointerAt(int row, int column, MatrixElement newElement)
			throws IllegalArgumentException{
		checkLegalCoords(row,column);
		this.values[row][column] = newElement;
	}
	
	public void setDefaultElementValue(MatrixElement value) {
		this.defaultElement = value.createNewInstance();
	}
	public void setDefaultElementPointer(MatrixElement pointer) {
		this.defaultElement = pointer;
	}
	
	public Matrix mAdd(Matrix... matrices) throws IllegalArgumentException {
		for(Matrix matrix : matrices){
			if(matrix.getDefaultElementValue().getClass() != this.getDefaultElementValue().getClass())
				throw new IllegalArgumentException("Matrix Element types must match!");
			for(int i = 0 ; i < ((this.rows <= matrix.getRows())?this.rows:matrix.getRows()) ; i++){
				for(int j = 0 ; j < ((this.columns<=matrix.getColumns())?this.columns:matrix.getColumns()) ; j++){
					this.setElementValueAt(i, j, 
							this.getElementValueAt(i, j).add(matrix.getElementValueAt(i, j))
							);
				}
			}
		}
		return this;
	}
	
	public Matrix mSubtract(Matrix matrix) throws IllegalArgumentException{
		if(matrix.getDefaultElementValue().getClass() != this.getDefaultElementValue().getClass())
			throw new IllegalArgumentException("Matrix Element types must match!");
		for(int i = 0 ; i < ((this.rows <= matrix.getRows())?this.rows:matrix.getRows()) ; i++){
			for(int j = 0 ; j < ((this.columns<=matrix.getColumns())?this.columns:matrix.getColumns()) ; j++){
				this.setElementValueAt(i, j, 
						this.getElementValueAt(i, j).subtract(matrix.getElementValueAt(i, j))
						);
			}
		}
		return this;	
	}
	
	private static MatrixElement sumMMultiply(Matrix matrixA, Matrix matrixB, int rowOfA, int columnOfB)
		throws IllegalArgumentException{
		
		if(!matrixA.getDefaultElementValue().isCompatibleWith(matrixB.getDefaultElementValue()))
			throw new IllegalArgumentException("Matrix element types must match");
		
		if(matrixA.getColumns() != matrixB.getRows())
			throw rowColException;
		
		int count = matrixA.getColumns();
		
		MatrixElement sum = matrixA.getDefaultElementValue().zeroValue().createNewInstance();
		MatrixElement temp;
		for(int i = 0 ; i < count ; i++){
			temp = matrixA.getElementValueAt(rowOfA, i).multiply(matrixB.getElementValueAt(i, columnOfB));
			sum.add(temp);
		}
		return sum;
	}
	
	private void checkElementsCompatible(MatrixElement a, MatrixElement b)
		throws IllegalArgumentException{
		if(a.getClass() != b.getClass())
			throw new IllegalArgumentException("Matrix Element types do not match!");
	}
	
	public Matrix mMultiply(Matrix matrix) throws IllegalArgumentException {
		
		if(matrix.getRows() != this.getColumns())
			throw rowColException;
		
		checkElementsCompatible(this.getDefaultElementValue(),matrix.getDefaultElementValue());
		
		Matrix returnMatrix = new DenseMatrix(this.rows, matrix.getColumns(),this.getDefaultElementValue());
		
		for(int i = 0 ; i < this.rows ; i++){
			for(int j = 0 ; j < matrix.getColumns() ; j++){
				returnMatrix.setElementValueAt(i, j, 
						sumMMultiply(this, matrix, i, j)
				);
			}
		}
		return returnMatrix;
	}
	
	public Matrix scalarMultiply(MatrixElement scale) {
		for(int i = 0 ; i < this.getRows() ; i++)
			for(int j = 0 ; j < this.getColumns() ; j++)
				this.getElementPointerAt(i, j).multiply(
						scale
				);
		return this;
	}

	@Override
	public MatrixElement getDeterminant() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Matrix getEigenvalues() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Matrix getInverse() {
		// TODO Auto-generated method stub
		return null;
	}
	
	public Matrix getTranspose() {
		Matrix returnMatrix = new DenseMatrix(this.columns, this.rows, this.getDefaultElementValue());
		for(int i = 0 ; i < this.rows ; i++)
			for(int j = 0 ; j < this.columns ; j++)
				returnMatrix.setElementValueAt(j, i, 
						this.getElementValueAt(i, j)
				);
		return returnMatrix;
	}
	
	public String toString(){
		String returnStr =
			( this.rows + " by " + this.columns + " \"" + this.getClass().getName() + "\" " ) +
			("default element = " + this.getDefaultElementValue());
		for(int i = 0 ; i < this.rows ; i ++){
			returnStr += "\n|";
			for(int j = 0 ; j < this.columns ; j++){
				returnStr += ( this.getElementValueAt(i, j) + "|" );
			}
		}
		return returnStr;
	}
	
	public static void main(String [] args){
		int rows = 2;
		int cols = 2;
		MatrixElement ellie = new TestElement(0);
		System.out.println("making a " + rows + " by " + cols + " matrix: ");
		Matrix myMat = new DenseMatrix(rows, cols, ellie);
		System.out.println("Matrix: " + myMat);
		System.out.println("Filling with random values: ");
		for(int i = 0 ; i < myMat.getRows() ; i ++){
			for(int j = 0 ; j < myMat.getColumns() ; j++){
				float value = (float)Math.random();
				myMat.setElementValueAt(i, j, new TestElement( value ) );
			}
		}
		System.out.println(myMat);
		System.out.println("Getting the transpose...");
		Matrix myMatB = myMat.getTranspose();
		System.out.println(myMatB);
		System.out.println("Adding them");
		myMat.mAdd(myMatB);
		System.out.println(myMat);
		
		myMat = myMatB.getTranspose();
		
		System.out.println("\n\nMultiplying " + myMat + "\n with " + myMatB);
		
		System.out.println(myMat.mMultiply(myMatB));
		
		
		
	}
	
}
